﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using ObjectModel = System.Collections.ObjectModel;

namespace GBricks.Collections
{
  [Pure]
  public static class Sequence
  {
    public const int InvalidIndex = -1;

    public static bool IsSingle<T>(this IEnumerable<T> source) {
      Argument.NotNull(source, "source");

      var readOnlyCollection = source as IReadOnlyCollection<T>;
      if(readOnlyCollection != null) {
        return readOnlyCollection.Count == 1;
      }//if

      var collection = source as ICollection<T>;
      if(collection != null) {
        return collection.Count == 1;
      }//if

      using(var enumerator = source.GetEnumerator()) {
        return enumerator.MoveNext() && !enumerator.MoveNext();
      }//using
    }

    public static bool IsMany<T>(this IEnumerable<T> source) {
      Argument.NotNull(source, "source");

      var readOnlyCollection = source as IReadOnlyCollection<T>;
      if(readOnlyCollection != null) {
        return readOnlyCollection.Count > 1;
      }//if

      var collection = source as ICollection<T>;
      if(collection != null) {
        return collection.Count > 1;
      }//if

      using(var enumerator = source.GetEnumerator()) {
        return enumerator.MoveNext() && enumerator.MoveNext();
      }//using
    }

    public static int IndexOf<T>(this IEnumerable<T> source, T item, IEqualityComparer<T> comparer) {
      Argument.NotNull(source, "source");
      Contract.Ensures(Contract.Result<int>() >= 0 || Contract.Result<int>() == InvalidIndex);
      return IndexOf(source, item, 0, comparer);
    }

    public static int IndexOf<T>(this IEnumerable<T> source, T item, int startIndex = 0, IEqualityComparer<T> comparer = null) {
      Argument.NotNull(source, "source");
      Argument.OutOfRange(startIndex >= 0, startIndex, "startIndex");
      Contract.Ensures(Contract.Result<int>() >= 0 || Contract.Result<int>() == InvalidIndex);

      if(startIndex == 0 && comparer == null) {
        var list = source as IList<T>;
        if(list != null) {
          return list.IndexOf(item);
        }//if
      }//if

      var cmp = comparer ?? EqualityComparer<T>.Default;
      var index = 0;
      foreach(var element in source) {
        if(index >= startIndex && cmp.Equals(element, item)) {
          return index;
        }//if
        index++;
      }//for
      return InvalidIndex;
    }

    public static IEnumerable<T> BreadthFirst<T>(T root, Converter<T, IEnumerable<T>> selector) {
      Argument.NotNull(selector, "selector");
      Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);
      return BreadthFirstIterator(root, selector);
    }

    private static IEnumerable<T> BreadthFirstIterator<T>(T root, Converter<T, IEnumerable<T>> selector) {
      Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);
      var queue = new Queue<T>();
      queue.Enqueue(root);
      while(queue.Any()) {
        var current = queue.Dequeue();
        yield return current;
        foreach(var item in selector(current)) {
          queue.Enqueue(item);
        }//for
      }//while
    }

    public static IEnumerable<T> DepthFirst<T>(T root, Converter<T, IEnumerable<T>> selector) {
      Argument.NotNull(selector, "selector");
      Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);
      return DepthFirstIterator(root, selector);
    }

    private static IEnumerable<T> DepthFirstIterator<T>(T root, Converter<T, IEnumerable<T>> selector) {
      Argument.NotNull(selector, "selector");
      Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

      yield return root;
      foreach(var children in selector(root)) {
        foreach(var item in DepthFirst(children, selector)) {
          yield return item;
        }//for
      }//for
    }

    [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "pattern", Justification = Justifications.ByDesign)]
    public static T[] GetEmptyArray<T>(T pattern = default(T)) {
      Contract.Ensures(Contract.Result<T[]>() != null);
      return EmptyArray<T>.Value;
    }

    public static IList<T> AsList<T>(this IEnumerable<T> items) {
      Contract.Ensures(Contract.Result<IList<T>>() != null);
      return items as IList<T> ?? new List<T>(items ?? Enumerable.Empty<T>());
    }

    public static ReadOnlyCollection<T> AsReadOnly<T>(this IEnumerable<T> items) {
      Contract.Ensures(Contract.Result<ReadOnlyCollection<T>>() != null);
      return items as ReadOnlyCollection<T> ?? new ReadOnlyCollection<T>(AsList(items));
    }

    public static IReadOnlyCollection<T> AsReadOnlyCollection<T>(this IEnumerable<T> items) {
      Contract.Ensures(Contract.Result<IReadOnlyCollection<T>>() != null);
      return items as IReadOnlyCollection<T> ?? new ReadOnlyCollection<T>(AsList(items));
    }

    public static IReadOnlyList<T> AsReadOnlyList<T>(this IEnumerable<T> items) {
      Contract.Ensures(Contract.Result<IReadOnlyList<T>>() != null);
      return items as IReadOnlyList<T> ?? new ReadOnlyCollection<T>(AsList(items));
    }

    public static ReadOnlyKeyedCollection<TKey, TItem> AsReadOnly<TKey, TItem>(this KeyedCollection<TKey, TItem> source) {
      Argument.NotNull(source, "source");
      Contract.Ensures(Contract.Result<ReadOnlyKeyedCollection<TKey, TItem>>() != null);
      return new ReadOnlyKeyedCollection<TKey, TItem>(source);
    }
  }
}
